#include "Spark/Net/EventLoop.h"
#include "Spark/Net/TimerQueue.h"
#include "Spark/Net/Channel.h"
#include "Spark/Utils/TimePoint.h"
#include <sys/timerfd.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <stdexcept>

static int createTimerFD()
{
    int timerFD = ::timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
    if (timerFD < 0)
    {
        throw std::runtime_error(strerror(errno));
    }
    return timerFD;
}

/**
 * @brief Helper function to calculate struct timespec
 * between TimePoint::Now() to given TimePoint
 * @param  when XXX
 * @return timespec
 */
static timespec timeIntervalFromNow(TimePoint when)
{
    timespec ts = TimePoint::diffAsTimeSpec(TimePoint::Now(), when);
    if (ts.tv_nsec < 100000000)
    {
        ts.tv_nsec = 100000000;
    }
    return ts;
}

/**
 * @brief Reset timerfd to notify next time
 * @param  timerFD the timerfd to modify
 * @param  when the time to notify
 */
static void resetTimerfd(int timerFD, TimePoint when)
{
    itimerspec newVal;
    itimerspec oldVal;
    memset(&newVal, 0, sizeof(itimerspec));
    memset(&oldVal, 0, sizeof(itimerspec));
    newVal.it_value = timeIntervalFromNow(when);
    int ret = ::timerfd_settime(timerFD, 0, &newVal, &oldVal);
    if (ret)
    {
        throw std::runtime_error(strerror(errno));
    }
}

/**
 * @brief Perform a read operation on timerfd
 * in order to remove readable events from epoll
 * @param  timerFD the timerfd to read
 */
static void readTimerfd(int timerFD)
{
    uint64_t dummy;
    ssize_t n = ::read(timerFD, &dummy, sizeof(dummy));
    if (n != sizeof(dummy))
    {
        throw std::runtime_error("readTimerFd unexpected bytes read!!");
    }
}

void TimerQueue::handleRead()
{
    assert(loop_->isInLoopThread());
    auto now = TimePoint::Now();
    readTimerfd(timerFD_);
    auto expiredList = getExpired(now);
    for (const auto& t : expiredList)
    {
        if (timerIDSet_.find(t->id()) != timerIDSet_.end())
        {
            t->run();
        }
    }
    recycleTimer(expiredList, now);
}

template <class P, class R>
TimerID TimerQueue::addTimer(Task callback,
                             TimePoint when,
                             TimeInterval<P, R> interval)
{
    auto interval_ = toSysDur(interval);
    auto timer = std::make_shared<Timer>(std::move(callback), when, interval_);
    loop_->runInLoop(std::bind(&TimerQueue::addTimerInLoop, this, timer));
    return timer->id();
}

template TimerID TimerQueue::addTimer(Task,
                                      TimePoint,
                                      TimeInterval<long double, std::ratio<1l, 1l>>);
template TimerID TimerQueue::addTimer(Task,
                                      TimePoint,
                                      TimeInterval<long, std::ratio<1l, 1l>>);
template TimerID TimerQueue::addTimer(Task,
                                      TimePoint,
                                      TimeInterval<long, std::milli>);
template TimerID TimerQueue::addTimer(Task,
                                      TimePoint,
                                      TimeInterval<long, std::micro>);
template TimerID TimerQueue::addTimer(Task,
                                      TimePoint,
                                      TimeInterval<long, std::nano>);

void TimerQueue::addTimerInLoop(TimerPtr timer)
{
    assert(loop_->isInLoopThread());
    timerIDSet_.insert(timer->id());
    if (pushTimerHeap(timer))
    {
        resetTimerfd(timerFD_, timer->when());
    }
}

bool TimerQueue::pushTimerHeap(TimerPtr timer)
{
    assert(loop_->isInLoopThread());
    bool isEarliestTimer = (timers_.size() == 0 || *timer < *timers_.top());
    timers_.push(timer);
    return isEarliestTimer;
}

TimerPtrList TimerQueue::getExpired(TimePoint when)
{
    TimerPtrList expired;
    while (!timers_.empty())
    {
        if (timers_.top()->when() < when)
        {
            expired.push_back(timers_.top());
            timers_.pop();
        }
        else
            break;
    }
    return expired;
}

TimerQueue::TimerQueue(EventLoop* loop)
    : loop_(loop),
      timerFD_(createTimerFD()),
      timerChannel_(new Channel(loop, timerFD_)),
      timers_(),
      timerIDSet_()
{
    timerChannel_->setCallback(CBType::READ, [this] { handleRead(); });
    timerChannel_->addReadEvent();
}

TimerQueue::~TimerQueue()
{
    auto ch = timerChannel_;
    auto fd = timerFD_;
    loop_->runInLoop([ch, fd] {
        ch->disableAll();
        ch->remove();
        ::close(fd);
    });
}

void TimerQueue::disableTimer(TimerID id)
{
    loop_->runInLoop([this, id] { timerIDSet_.erase(id); });
}

void TimerQueue::reset()
{
    loop_->runInLoop([this] {
        timerChannel_->disableAll();
        timerChannel_->remove();
        ::close(timerFD_);
        timerFD_ = createTimerFD();
        timerChannel_ = std::make_shared<Channel>(loop_, timerFD_);
        timerChannel_->setCallback(CBType::READ, [this] { handleRead(); });
        timerChannel_->addReadEvent();
        if (!timers_.empty())
        {
            auto nextExpireTime = timers_.top()->when();
            resetTimerfd(timerFD_, nextExpireTime);
        }
    });
}

void TimerQueue::recycleTimer(const TimerPtrList& expired, TimePoint when)
{
    assert(loop_->isInLoopThread());
    for (const auto& t : expired)
    {
        auto iter = timerIDSet_.find(t->id());
        if (iter != timerIDSet_.end())
        {
            if (t->isRepeat())
            {
                t->restart(when);
                pushTimerHeap(t);
            }
            else
            {
                timerIDSet_.erase(iter);
            }
        }
    }
    if (!timers_.empty())
    {
        auto nextExpireTime = timers_.top()->when();
        if (nextExpireTime > TimePoint::Now())
            resetTimerfd(timerFD_, nextExpireTime);
    }
}
